#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <cassert>

namespace impl
{
const char encTable[]{
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};
const char decTable[256]{
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60,
    61, -1, -1, -1, 65, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
    11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1,
    -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
    43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1};
const char padding = '=';

class Base64
{
  public:
    std::string to_base64(std::vector<uint8_t> const& data);
    std::vector<uint8_t> from_base64(std::string data);
};

std::string Base64::to_base64(std::vector<uint8_t> const& data)
{
    std::string result;
    result.resize((data.size() / 3 + ((data.size() % 3 > 0) ? 1 : 0)) * 4);
    auto ptr = &result[0];
    size_t i = 0;
    size_t j = 0;
    while (j++ < data.size() / 3)
    {
        uint32_t value = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2];
        i += 3;
        *ptr++ = encTable[(value & 0x00fc0000) >> 18];
        *ptr++ = encTable[(value & 0x0003f000) >> 12];
        *ptr++ = encTable[(value & 0x00000fc0) >> 6];
        *ptr++ = encTable[(value & 0x0000003f)];
    }

    auto rest = data.size() - i;
    if (rest == 1)
    {
        *ptr++ = encTable[(data[i] & 0x000000fc) >> 2];
        *ptr++ = encTable[(data[i] & 0x00000003) << 4];
        *ptr++ = padding;
        *ptr++ = padding;
    }
    else if (rest = 2)
    {
        uint32_t value = (data[i] << 8) | data[i + 1];
        *ptr++ = encTable[(value & 0x0000fc00) >> 10];
        *ptr++ = encTable[(value & 0x000003f0) >> 4];
        *ptr++ = encTable[(value & 0x0000000f) << 2];
        *ptr++ = padding;
    }

    return result;
}

std::vector<uint8_t> Base64::from_base64(std::string data)
{
    size_t paddingLen = data.size() % 4;
    if (paddingLen == 0)
    {
        if (data[data.size() - 1] == padding)
            paddingLen++;
        if (data[data.size() - 2] == padding)
            paddingLen++;
    }
    else
    {
        data.append(2, padding);
    }

    std::vector<uint8_t> result;
    result.resize((data.length() / 4) * 3 - paddingLen);
    auto ptr = &result[0];

    size_t i = 0;
    size_t j = 0;
    while (j++ < data.size() / 4)
    {
        uint8_t c1 = decTable[static_cast<int>(data[i++])];
        uint8_t c2 = decTable[static_cast<int>(data[i++])];
        uint8_t c3 = decTable[static_cast<int>(data[i++])];
        uint8_t c4 = decTable[static_cast<int>(data[i++])];

        // invalid char is -1;
        if (c1 == -1 || c2 == -1 || c3 == -1 || c4 == -1)
        {
            throw std::runtime_error("invalid base64 string");
        }
        if (c4 == padding && c3 == padding)
        {
            uint32_t value = (c1 << 6) | c2;
            *ptr++ = (value >> 4) & 0x000000ff;
        }
        else if (c4 == padding)
        {
            uint32_t value = (c1 << 12) | (c2 << 6) | c3;
            *ptr++ = (value >> 10) & 0x000000ff;
            *ptr++ = (value >> 2) & 0x000000ff;
        }
        else
        {
            uint32_t value = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
            *ptr++ = (value >> 16) & 0x000000ff;
            *ptr++ = (value >> 8) & 0x000000ff;
            *ptr++ = value & 0x000000ff;
        }
    }
    return result;
}

struct converter
{
    static std::vector<uint8_t> from_string(std::string_view data)
    {
        std::vector<uint8_t> result;
        std::copy(data.begin(), data.end(), std::back_inserter(result));
        return result;
    }

    static std::string from_range(std::vector<uint8_t> const& data)
    {
        std::string result;
        std::copy(data.begin(), data.end(), std::back_inserter(result));
        return result;
    }
};

}  // namespace impl

std::ostream& operator<<(std::ostream& os, std::vector<uint8_t> const& v)
{
    os << "decoded vector: ";
    for (auto const& c : v)
    {
        os << c;
    }
    return os;
}

using namespace std;
int main(int argc, char* argv[])
{
    std::vector<std::vector<uint8_t>> data{{'s'},
                                           {'s', 'a'},
                                           {'s', 'a', 'm'},
                                           {'s', 'a', 'm', 'p'},
                                           {'s', 'a', 'm', 'p', 'l'},
                                           {'s', 'a', 'm', 'p', 'l', 'e'}};

    impl::Base64 enc;

    for (auto const& v : data)
    {
        auto encv = enc.to_base64(v);
        cout << "encoded vector: " << encv << endl;
        auto decv = enc.from_base64(encv);
        cout << decv << endl;
        assert(v == decv);
    }

    auto text = "handy_snippets";
    auto textenc = enc.to_base64(impl::converter::from_string(text));
    cout << "encoded text: " << textenc << endl;
    auto textdec = impl::converter::from_range(enc.from_base64(textenc));
    cout << "decoded text: " << textdec << endl;
    assert(text == textdec);
    return 0;
}